<!DOCTYPE html>
<html>
  <head>
     
    <meta charset="UTF-8">
    <title>JUC 2 - 测开、Java后端最常见、最核心的面试题总结</title>
    <link rel="shortcut icon" href="/static/img/icon.png">
    <link rel="icon" href="/static/img/icon.png" sizes="192x192"/>
    
<link rel="stylesheet" href="/static/kico.css">
<link rel="stylesheet" href="/static/hingle.css">

    
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css">

    <meta name="viewport" content="width=device-width, maximum-scale=1, initial-scale=1"/>
    <meta property="og:site_name" content="测开、Java后端最常见、最核心的面试题总结">
    <meta property="og:title" content="JUC 2"/>
    
<meta name="generator" content="Hexo 7.3.0"></head>

  <body>
    <header>
    <div class="head-title">
        <h4>测开、Java后端最常见、最核心的面试题总结</h4>
    </div>
    <div class="head-action">
        <div class="toggle-btn"></div>
        <div class="light-btn"></div>
        <div class="search-btn"></div>
    </div>
    <form class="head-search" method="post">
        <input type="text" name="s" placeholder="搜索什么？">
    </form>
    <nav class="head-menu">
        <a href="/">首页</a>
        
    </nav>
</header>

    <main>
    <div class="wrap min">
        <section class="post-title">
            <h2>JUC 2</h2>
            <div class="post-meta">
                <time class="date">2024.07.25</time>
            
            </div>
        </section>
        <article class="post-content">
        
            <h1><span id="16什么是悲观锁">16.什么是悲观锁？</span></h1><p>悲观锁总是假设最坏的情况，认为共享资源每次被访问的时候就会出现问题(比如共享数据被修改)，所以每次在获取资源操作的时候都会上锁，这样其他线程想拿到这个资源就会阻塞直到锁被上一个持有者释放。也就是说，<strong>共享资源每次只给一个线程使用，其它线程阻塞，用完后再把资源转让给其它线程</strong>。</p>
<p>像 Java 中<code>synchronized</code>和<code>ReentrantLock</code>等独占锁就是悲观锁思想的实现。</p>
<p>高并发的场景下，激烈的锁竞争会造成线程阻塞，大量阻塞线程会导致系统的上下文切换，增加系统的性能开销。并且，悲观锁还可能会存在死锁问题，影响代码的正常运行。</p>
<h1><span id="17什么是乐观锁">17.什么是乐观锁？</span></h1><p>乐观锁总是假设最好的情况，认为共享资源每次被访问的时候不会出现问题，线程可以不停地执行，无需加锁也无需等待，只是在提交修改的时候去验证对应的资源（也就是数据）是否被其它线程修改了（具体方法可以使用版本号机制或 CAS 算法）。</p>
<p>高并发的场景下，乐观锁相比悲观锁来说，不存在锁竞争造成线程阻塞，也不会有死锁的问题，在性能上往往会更胜一筹。但是，如果冲突频繁发生（写占比非常多的情况），会频繁失败和重试，这样同样会非常影响性能，导致 CPU 飙升。</p>
<p>理论上来说：</p>
<ul>
<li>悲观锁通常多用于写比较多的情况（多写场景，竞争激烈），这样可以避免频繁失败和重试影响性能，悲观锁的开销是固定的。不过，如果乐观锁解决了频繁失败和重试这个问题的话（比如<code>LongAdder</code>），也是可以考虑使用乐观锁的，要视实际情况而定。</li>
<li>乐观锁通常多用于写比较少的情况（多读场景，竞争较少），这样可以避免频繁加锁影响性能。不过，乐观锁主要针对的对象是单个共享变量（参考<code>java.util.concurrent.atomic</code>包下面的原子变量类）。</li>
</ul>
<h1><span id="18如何实现乐观锁">18.如何实现乐观锁？</span></h1><p><strong>版本号机制：</strong></p>
<p>一般是在数据表中加上一个数据版本号 <code>version</code> 字段，表示数据被修改的次数。当数据被修改时，<code>version</code> 值会加一。当线程 A 要更新数据值时，在读取数据的同时也会读取 <code>version</code> 值，在提交更新时，若刚才读取到的 version 值为当前数据库中的 <code>version</code> 值相等时才更新，否则重试更新操作，直到更新成功。</p>
<p><strong>CAS算法：</strong></p>
<p>CAS 的全称是 <strong>Compare And Set（Swap）（比较与交换）</strong> ，用于实现乐观锁，被广泛应用于各大框架中。CAS 的思想很简单，就是用一个预期值和要更新的变量值进行比较，两值相等才会进行更新。</p>
<p>CAS 是一个原子操作，底层依赖于一条 CPU 的原子指令。</p>
<p>CAS 涉及到三个操作数：</p>
<ul>
<li><strong>V</strong>：要更新的变量值(Var)</li>
<li><strong>E</strong>：预期值(Expected)</li>
<li><strong>N</strong>：拟写入的新值(New)</li>
</ul>
<p>当且仅当 V 的值等于 E 时，CAS 通过原子方式用新值 N 来更新 V 的值。如果不等，说明已经有其它线程更新了 V，则当前线程放弃更新。</p>
<h1><span id="19cas-有什么问题如何解决">19.CAS 有什么问题？如何解决？</span></h1><p><strong>ABA 问题：</strong></p>
<p>如果一个位置的值原来是 A，后来被改为 B，再后来又被改回 A，那么进行 CAS 操作的线程将无法知晓该位置的值在此期间已经被修改过。</p>
<blockquote>
<p>怎么解决ABA问题？</p>
</blockquote>
<p>可以使用版本号&#x2F;时间戳的方式来解决 ABA 问题。</p>
<p>比如说，每次变量更新时，不仅更新变量的值，还更新一个版本号。CAS 操作时不仅要求值匹配，还要求版本号匹配。</p>
<p><strong>循环性能开销：</strong></p>
<p>自旋 CAS，如果一直循环执行，一直不成功，会给 CPU 带来非常大的执行开销。</p>
<blockquote>
<p>怎么解决循环性能开销问题？</p>
</blockquote>
<p>在 Java 中，很多使用自旋 CAS 的地方，会有一个自旋次数的限制，超过一定次数，就停止自旋。</p>
<p><strong>只能保证一个变量的原子操作：</strong></p>
<p>CAS 保证的是对一个变量执行操作的原子性，如果对多个变量操作时，CAS 目前无法直接保证操作的原子性的。</p>
<blockquote>
<p>怎么解决只能保证一个变量的原子操作问题？</p>
</blockquote>
<ul>
<li>可以考虑改用锁来保证操作的原子性</li>
<li>可以考虑合并多个变量，将多个变量封装成一个对象</li>
</ul>
<h1><span id="20threadlocal-是什么">20.ThreadLocal 是什么？</span></h1><p>ThreadLocal是 Java 中提供的一种用于实现线程局部变量的工具类。它允许每个线程都拥有自己的独立副本，从而实现线程隔离，用于解决多线程中共享对象的线程安全问题。</p>
<p>在 Web 应用中，可以使用 ThreadLocal 存储用户会话信息，这样每个线程在处理用户请求时都能方便地访问当前用户的会话信息。</p>
<p>在数据库操作中，可以使用 ThreadLocal 存储数据库连接对象，每个线程有自己独立的数据库连接，从而避免了多线程竞争同一数据库连接的问题。</p>
<p>使用 ThreadLocal 通常分为四步：</p>
<p>①、创建 ThreadLocal</p>
<p>②、设置 ThreadLocal 的值</p>
<p>③、获取 ThreadLocal 的值</p>
<p>④、删除 ThreadLocal 的值</p>
<h1><span id="21threadlocal-内存泄露是怎么回事">21.ThreadLocal 内存泄露是怎么回事？</span></h1><p>通常情况下，随着线程 Thread 的结束，其内部的 ThreadLocalMap 也会被回收，从而避免了内存泄漏。</p>
<p>但如果一个线程一直在运行，并且其 <code>ThreadLocalMap</code> 中的 Entry.value 一直指向某个强引用对象，那么这个对象就不会被回收，从而导致内存泄漏。当 Entry 非常多时，可能就会引发更严重的内存溢出问题。</p>
<p> <strong>那怎么解决内存泄漏问题呢？</strong></p>
<p>很简单，使用完 ThreadLocal 后，及时调用 <code>remove()</code> 方法释放内存空间。</p>
<p><code>remove()</code> 方法会将当前线程的 ThreadLocalMap 中的所有 key 为 null 的 Entry 全部清除，这样就能避免内存泄漏问题。</p>
<h1><span id="22什么是线程池工作流程是怎样的">22.什么是线程池，工作流程是怎样的？</span></h1><p>顾名思义，线程池就是管理一系列线程的资源池。当有任务要处理时，直接从线程池中获取线程来处理，处理完之后线程并不会立即被销毁，而是等待下一个任务。</p>
<p>当应用程序提交一个任务时，线程池会根据当前线程的状态和参数决定如何处理这个任务。</p>
<ul>
<li>如果线程池中的核心线程都在忙，并且线程池未达到最大线程数，新提交的任务会被放入队列中进行等待。</li>
<li>如果任务队列已满，且当前线程数量小于最大线程数，线程池会创建新的线程来处理任务。</li>
</ul>
<p>空闲的线程会从任务队列中取出任务来执行，当任务执行完毕后，线程并不会立即销毁，而是继续保持在池中等待下一个任务。</p>
<p>当线程空闲时间超出指定时间，且当前线程数量大于核心线程数时，线程会被回收。</p>
<p><strong>使用场景：</strong></p>
<p><strong>①、快速响应用户请求</strong></p>
<p>当用户发起一个实时请求，服务器需要快速响应，此时如果每次请求都直接创建一个线程，那么线程的创建和销毁会消耗大量的系统资源。</p>
<p><strong>②、快速处理批量任务</strong></p>
<h1><span id="23线程池常见参数有哪些">23.线程池常见参数有哪些？</span></h1><p><strong>corePoolSize</strong>（核心线程池大小）：</p>
<ul>
<li>线程池中保留的最小线程数，即使这些线程是空闲的，也不会被回收。</li>
</ul>
<p><strong>maximumPoolSize</strong>（最大线程池大小）：</p>
<ul>
<li>线程池允许创建的最大线程数。当任务队列已满且活跃线程数达到 <code>corePoolSize</code> 时，如果还有新任务到达，且当前线程数未达到 <code>maximumPoolSize</code>，则会继续创建新线程。</li>
</ul>
<p><strong>keepAliveTime</strong>（线程空闲时间）：</p>
<ul>
<li>当线程池中的线程数量超过 <code>corePoolSize</code> 时，多余的空闲线程（即线程数大于 <code>corePoolSize</code> 的线程）在等待新任务时最多保持空闲的时间。如果空闲时间超过 <code>keepAliveTime</code>，这些空闲线程将被终止。</li>
</ul>
<p><strong>unit</strong>（时间单位）：</p>
<ul>
<li><code>keepAliveTime</code> 参数的时间单位，可以是 <code>TimeUnit</code> 枚举类的任意值，如 <code>TimeUnit.SECONDS</code>、<code>TimeUnit.MILLISECONDS</code> 等。</li>
</ul>
<p><strong>workQueue</strong>（任务队列）：</p>
<ul>
<li><p>用于保存等待执行任务的队列。当所有核心线程都在执行任务时，新任务会被放入此队列中等待执行。</p>
</li>
<li><p>常用的队列类型有：</p>
<p>① ArrayBlockingQueue：一个有界的先进先出的阻塞队列，底层是一个数组，适合固定大小的线程池。<br>② LinkedBlockingQueue：底层数据结构是链表，如果不指定大小，默认大小是 Integer.MAX_VALUE，相当于一个无界队列。<br>③ PriorityBlockingQueue：一个支持优先级排序的无界阻塞队列。任务按照其自然顺序或通过构造器给定的 Comparator 来排序。<br>适用于需要按照给定优先级处理任务的场景，比如优先处理紧急任务。<br>④ DelayQueue：类似于 PriorityBlockingQueue，由二叉堆实现的无界优先级阻塞队列。<br>⑤ SynchronousQueue：实际上它不是一个真正的队列，因为没有容量。每个插入操作必须等待另一个线程的移除操作，同样任何一个移除操作都必须等待另一个线程的插入操作。</p>
</li>
</ul>
<p><strong>threadFactory</strong>（线程工厂）：</p>
<ul>
<li>用于创建新线程的工厂。可以通过自定义 <code>ThreadFactory</code> 实现来设置线程的名称、优先级等属性。</li>
<li>默认的线程工厂会创建非守护线程，且优先级为正常的线程。</li>
</ul>
<p><strong>handler</strong>（拒绝策略）：</p>
<ul>
<li>当线程池和任务队列都已满，并且无法执行新的任务时，线程池将执行拒绝策略。<code>handler</code> 指定了线程池拒绝任务时的处理方式。</li>
<li>常见的拒绝策略有：<ul>
<li><code>AbortPolicy</code>：默认策略，直接抛出 <code>RejectedExecutionException</code> 异常。</li>
<li><code>CallerRunsPolicy</code>：由调用线程处理该任务（比如主线程）。</li>
<li><code>DiscardPolicy</code>：直接丢弃任务，不抛出异常。</li>
<li><code>DiscardOldestPolicy</code>：丢弃队列中等待时间最长的任务，然后尝试重新提交新任务。</li>
</ul>
</li>
</ul>
<h1><span id="24线程池在使用的时候需要注意什么">24.线程池在使用的时候需要注意什么？</span></h1><p>①、选择合适的线程池大小</p>
<ul>
<li><strong>过小</strong>的线程池可能会导致任务一直在排队</li>
<li><strong>过大</strong>的线程池可能会导致大家都在竞争 CPU 资源，增加上下文切换的开销</li>
</ul>
<p>可以根据业务是 IO 密集型还是 CPU 密集型来选择线程池大小：</p>
<ul>
<li>CPU 密集型：指的是任务主要使用来进行大量的计算，没有什么导致线程阻塞。一般这种场景的线程数设置为 CPU 核心数+1。</li>
<li>IO 密集型：当执行任务需要大量的 io，比如磁盘 io，网络 io，可能会存在大量的阻塞，所以在 IO 密集型任务中使用多线程可以大大地加速任务的处理。一般线程数设置为 2*CPU 核心数。</li>
</ul>
<p>②、任务队列的选择</p>
<ul>
<li>使用有界队列可以避免资源耗尽的风险，但是可能会导致任务被拒绝</li>
<li>使用无界队列虽然可以避免任务被拒绝，但是可能会导致内存耗尽</li>
</ul>
<p>一般需要设置有界队列的大小，比如 LinkedBlockingQueue 在构造的时候可以传入参数来限制队列中任务数据的大小，这样就不会因为无限往队列中扔任务导致系统的 oom。</p>
<p>③、尽量使用自定义的线程池，而不是使用 Executors 创建的线程池，因为 newFixedThreadPool 线程池由于使用了 LinkedBlockingQueue，队列的容量默认无限大，实际使用中出现任务过多时会导致内存溢出；newCachedThreadPool 线程池由于核心线程数无限大，当任务过多的时候会导致创建大量的线程，可能机器负载过高导致服务宕机。</p>

        </article>
        <section class="post-near">
            <ul>
                
                    <li>上一篇: <a href="/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F2/">操作系统2</a></li>
                
                
                    <li>下一篇: <a href="/Java%E5%9F%BA%E7%A1%802/">Java基础2</a></li>
                
            </ul>
        </section>
        
    
        <section class="post-author">
        
            <figure class="author-avatar">
                <img src="https://sdn.geekzu.org/avatar/d22eb460ecab37fcd7205e6a3c55c228?s=200&r=X&d=" alt="Hingle" />
            </figure>
        
            <div class="author-info">
                <h4>Hingle</h4>
                <p>请在这里设置你的作者信息</p>
            </div>
        </section>
    
    </div>
</main>

    <footer>
    <div class="buttons">
        <a class="to-top" href="#"></a>
    </div>
    <div class="wrap min">
        <section class="widget">
            <div class="row">
                <div class="col-m-4">
                    <h3 class="title-recent">最新文章：</h3>
                    <ul class="post-list"><li class="post-list-item"><a class="post-list-link" href="/%E9%A1%B9%E7%9B%AE/">项目</a></li><li class="post-list-item"><a class="post-list-link" href="/MySQL2/">MySQL2</a></li><li class="post-list-item"><a class="post-list-link" href="/Java17%E6%96%B0%E7%89%B9%E6%80%A7/">Java17新特性</a></li><li class="post-list-item"><a class="post-list-link" href="/Redis2/">Redis2</a></li><li class="post-list-item"><a class="post-list-link" href="/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C2/">计算机网络2</a></li><li class="post-list-item"><a class="post-list-link" href="/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F2/">操作系统2</a></li></ul>
                </div>
            </div>
        </section>
        <section class="sub-footer">
            <p>© 2024 <a href="/">测开、Java后端最常见、最核心的面试题总结</a>. All Rights Reserved. Theme By <a href="https://github.com/Dreamer-Paul/Hingle" target="_blank" rel="nofollow">Hingle</a>.</p>
        </section>
    </div>
</footer>


<script src="/static/kico.js"></script>
<script src="/static/hingle.js"></script>


<script>var hingle = new Paul_Hingle({"copyright":false,"night":false});</script>

  </body>
</html>
